/*
 * Project Beknyou
 * Copyright (c) 2010-2011 Saint Paul College, All Rights Reserved
 * Redistributions in source code form must reproduce the above
 * Copyright and this condition.
 * The contents of this file are subject to the GNU General Public
 * License, Version 2 (the "License"); you may not use this file
 * except in compliance with the License. A copy of the License is
 * available at http://www.opensource.org/licenses/gpl-license.php.
 * 
 */
package com.benkyou.client;

import com.benkyou.client.network.*;
import com.benkyou.common.messages.*;
import com.benkyou.common.Player;
import com.benkyou.client.systems.DefaultCamSystem;
import com.benkyou.client.systems.SkySystem;
import com.jme3.app.SimpleApplication;
import com.jme3.renderer.RenderManager;
import java.awt.Canvas;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.scene.Node;
import com.jme3.network.Client;
import java.util.ArrayList;
import com.jme3.network.Network;
import com.jme3.network.serializing.Serializer;
import com.benkyou.client.systems.LightManagmentSystem;
import com.benkyou.client.systems.TerrainSystem;
import com.benkyou.client.appstates.WorldPhysicsAppState;
import com.benkyou.client.systems.ImagingSystem;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.texture.Texture;
import java.util.Iterator;

/** 
 * This is the main game client class that should be pointed to when generating
 * jar files. GameClient is comprised of various 
 * {@link reference com.benkyou.client.systems} to create the default world. 
 * All objects to be created in 3 dimensional space should be made into a System
 * class and should accept a <b> GameClient</b> object in their constructor.
 * all object interaction should take place through the use of the <b>GameClient</b>
 * object, therefore every time a system is added to the game client appropiate
 * getters and setters should be written to allow interaction amongst the systems
 *  
 * @author Austin Allman, Adam Hess
 *
 */
public class GameClient extends SimpleApplication implements Runnable {

    /**
     * Creates a game client and connects it to the server and port passed into
     * the constructor.
     * TO-DO Create a efficient loading systems for attaching various listeners to
     * the client based on what the server is capable off
     * @param serverLocation ip of the server location
     * @param servePort The port number the server is running on
     */
    private Client myClient;
    private ArrayList<Player> playerList;
    private DefaultCamSystem camSys;
    private String playerName;
    private int playerID;
    private TerrainSystem terrainSystem;
    private SkySystem sky;
    private WorldPhysicsAppState worldPhysics;
    private FormHandler formHandler;
    private ArrayList<String> classList;
    private ImagingSystem vncImagingSystem;
    private ImagingSystem webcamImagingSystem;
    private Client webcamClient;
    private Client vncClient;
    private boolean up;
    private boolean newPlayer;
    private String playerUUID;
    private boolean markForPlayerSync;
    private ArrayList<Player> needsToBeSynced;
    private ArrayList<String> needsToBeDeleted;

    public GameClient(String serverLocation, int servePort) {
        this();
        //Logger.getLogger("").setLevel(Level.OFF);
        //Logger.getLogger(WebcamListener.class.toString()).setLevel(Level.OFF);

        Serializer.registerClass(WebcamMessage.class);
        Serializer.registerClass(VNCMessage.class);
        Serializer.registerClass(ChatClientMessage.class);
        Serializer.registerClass(LoginMessage.class);
        Serializer.registerClass(PlayerMessage.class);
        Serializer.registerClass(PlayerSyncMessage.class);
        Serializer.registerClass(PlayerDisconectMessage.class);
        up = false;
        newPlayer = true;
        this.needsToBeSynced = new ArrayList<Player>();
        this.needsToBeDeleted = new ArrayList<String>();
        markForPlayerSync = true;
        //Sets up a array list to store all of the players
        playerList = new ArrayList<Player>();
        try {
            myClient = Network.connectToServer("199.17.224.227", 6000);
            //myClient = Network.connectToServer("127.0.0.1", 6000);

            myClient.start();
            myClient.addMessageListener(new LoginListener(this), LoginMessage.class);
            myClient.addMessageListener(new ChatClientListener(this), ChatClientMessage.class);
            myClient.addMessageListener(new VNCListener(this), VNCMessage.class);
            //myClient.addMessageListener(new VNCListener(this), VNCMessage.class);
            myClient.addMessageListener(new WebcamListener(this), WebcamMessage.class);
            myClient.addMessageListener(new PlayerMessageListener(this), PlayerMessage.class, PlayerSyncMessage.class, PlayerDisconectMessage.class);
        } catch (IOException ex) {
            System.out.println("\n\n\n\n\n\n\nERROR CONNECTING!!!\n" + ex);
            Logger.getLogger(GameClient.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * No arguments constructor used during debugging and development to bybass
     * connecting to a server
     */
    public GameClient() {
        formHandler = new FormHandler(this);
        formHandler.initPanel();
    }

    /**
     * Main method to begin the game client
     * TO-DO parse through args for client settings such as server location and
     * server port.
     * @param args
     */
    public static void main(String args[]) {

        GameClient app = new GameClient("127.0.0.1", 6140);

        //GameClient app = new GameClient();
        //app.start();
    }

    /**
     * Sends a login message to the gaming server to login to the server
     * @param name name of the user 
     * @param password
     */
    public void login(String name, char[] password, int id) {
        getMyClient().send(new LoginMessage(name, password, id, null));
    }

    /**
     * 
     */
    @Override
    public void simpleInitApp() {
        //clears the debbuging messages
        guiNode.detachAllChildren();

        Quaternion vncYAW090   = new Quaternion().fromAngleAxis((7*FastMath.PI)/12,   new Vector3f(0,1,0));
        vncImagingSystem = new ImagingSystem(this, 140f,800,600);
        rootNode.attachChild(vncImagingSystem.getScreen());
        vncImagingSystem.getScreen().setLocalTranslation(-255f, -10f, 115f);
        vncImagingSystem.getScreen().setLocalRotation(vncYAW090);
        
        
        
        webcamImagingSystem = new ImagingSystem(this, 140f, 800, 600);
        rootNode.attachChild(webcamImagingSystem.getScreen());
       // (-117.84772, -3.4392495, -5.936719)
        webcamImagingSystem.getScreen().setLocalTranslation(-295f, -10f, -30f);
         Quaternion YAW090   = new Quaternion().fromAngleAxis((5*FastMath.PI)/12,   new Vector3f(0,1,0));
        webcamImagingSystem.getScreen().setLocalRotation(YAW090);

        //ImagingSystem benkyouBanner = new ImagingSystem(this, 40f, 640, 480);
        //rootNode.attachChild(benkyouBanner.getScreen());
        //benkyouBanner.getScreen().setLocalTranslation(-30f, 10f, -80);
        //Texture banner = assetManager.loadTexture("Textures/WelcomeBanner.jpg");
        //Material mat_stl = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        //mat_stl.setTexture("ColorMap", banner);
        //benkyouBanner.getScreen().setMaterial(mat_stl);
        /**
         * Sets flycam to work in JPanel and increases camera movement speed
         */
        camSys = new DefaultCamSystem(flyCam, cam);
        terrainSystem = new TerrainSystem(assetManager);
        rootNode.attachChild(terrainSystem.getTerrain());

        //craetes a simple sky for the world
        sky = new SkySystem(assetManager);
        rootNode.attachChild(getSky().getSky());
        //geom.setIgnoreTransform(false);


        //Sets up the list to be displayed in the class list
        classList = new ArrayList<String>();




        //stateManager.attach(worldPhysics);
        //Set up world lighting
       // LightManagmentSystem lightManagment = new LightManagmentSystem(this);




        //Temp lighting code to get the avatar to show up
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
        rootNode.addLight(sun);

        //myClient.send(new ChatClientMessage(playerName, "Has joined the server!", true, null));
        addMainPlayer();
        // imagingSystems.get(0).generateImageQuad();
        //Setting up the world physics
        worldPhysics = new WorldPhysicsAppState(this);
        stateManager.attach(worldPhysics);
        // worldPhysics.setupAllPlayers();
        System.out.println("Up before up?");
        up = true;
        //this.getWorldPhysics().getPhysicsSpace().enableDebug(assetManager);
    }

    /**
     * 
     * @param tpf
     */
    @Override
    public void simpleUpdate(float tpf) {
        if (newPlayer) {
            Iterator playerIterator = getPlayerList().iterator();
            while (playerIterator.hasNext()) {
                Player p = (Player) playerIterator.next();
                System.out.println(p + " newPlayer? " + newPlayer + " updateNeeded? " + p.isAtachementUpdate());
                if (p.isAtachementUpdate()) {
                    if (!p.isAttached()) {
                        p.setChatter(new Chatter(p.getName(), p.getUUID()));
                        this.formHandler.addNewUser(p.getChatter());
                        //p.getAvatar().setLocalTranslation(Player.spawnLocation);
                        worldPhysics.setupAllPlayers();
                        p.setAttached(true);
                        //rootNode.attachChild(p.getAvatar());
                    }
                    p.setAtachementUpdate(false);
                }

                if (p.isMarkedForDeletion()) {
                    System.out.println("Deleting player " + p);
                    rootNode.detachChild(p.getAvatar());
                    rootNode.detachChild(p);
                    p.detachAllChildren();
                    //getPlayerList().remove(p);
                    p.setMarkedForDeletion(false);
                    this.formHandler.removeUser(p.getChatter());
                }
                newPlayer = false;
            }
                if(needsToBeDeleted.size() > 0){
            for(String uuid : needsToBeDeleted){
                Player deletePlayer;
                deletePlayer = this.getPlayerByUUID(uuid);
                deletePlayer.setMarkedForDeletion(true);
            }
            newPlayer = true;
            needsToBeDeleted.clear();
        }
            

        }
        if (markForPlayerSync && up) {
            processSyncMessage(needsToBeSynced);
        }


    }

    /**
     * 
     * @param rm
     */
    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    /**
     * 
     */
    public void createGamePanel() {
        java.awt.EventQueue.invokeLater(this);
        formHandler = new FormHandler(this);
        formHandler.createLoginPanel();
        //  formHandler.initPanel();
        //formHandler.startGame();
    }

    public void run() {
    }

    /**
     * 
     * @param name
     * @param id
     */
    public void setMainPlayer(String name, int id, String uuid) {
        playerName = name;
        playerID = id;
        playerUUID = uuid;
    }

    /**
     * 
     */
    public void addMainPlayer() {
        Player p = addPlayer(getPlayerName(), getPlayerID(), getMainPlayerUUID()).setAsMainPlayer();
        getCamSys().setupChaseCam(p.getAvatar(), inputManager);
    }

    /**
     * 
     * @param name
     * @param id
     * @return
     */
    public Player addPlayer(String name, int id, String uuid) {
        Player p = new Player(this, id, name, uuid);
        getPlayerList().add(p);
        //rootNode.attachChild(playerList.get(playerList.size() - 1));
        return p;
    }

    /**
     * @return the webcamState
     */
    /**
     * @return the myClient
     */
    public Client getMyClient() {
        return myClient;
    }

    /**
     * @return the playerList
     */
    public synchronized ArrayList<Player> getPlayerList() {
        return playerList;
    }

    /**
     * @return the camSys
     */
    public DefaultCamSystem getCamSys() {
        return camSys;
    }

    /**
     * @return the playerName
     */
    public String getPlayerName() {
        return playerName;
    }

    /**
     * @return the playerID
     */
    public int getPlayerID() {
        return playerID;
    }

    /**
     * @return the terrain
     */
    public TerrainSystem getTerrainSystem() {
        return terrainSystem;
    }

    /**
     * @return the sky
     */
    public SkySystem getSky() {
        return sky;
    }

    /**
     * @return the worldPhysics
     */
    public WorldPhysicsAppState getWorldPhysics() {
        return worldPhysics;
    }

    /**
     * 
     * @return
     */
    public Node getRoot() {
        return rootNode;
    }

    /**
     * 
     * @return
     */
    public FormHandler getFormHandler() {
        return formHandler;
    }

    /**
     * 
     * @param name
     */
    public void addChatter(Chatter chatter) {

        formHandler.addNewUser(chatter);
    }

    /**
     * 
     * @param name
     * @param message
     */
    public void addChatText(String name, String message) {
        formHandler.addTextToChatBox(name, message);
    }

    /**
     * 
     * @param message
     */
    public void sendChatMessage(String message) {
        myClient.send(new ChatClientMessage(playerName, message));
    }

    /**
     * 
     * @return
     */
    public boolean isUp() {
        return up;
    }

    /**
     * 
     * @return
     */
    public ImagingSystem getVNCImagingSystem() {
        return vncImagingSystem;
    }

    /**
     * 
     * @return
     */
    public ImagingSystem getWebcamImagingSystem() {
        return webcamImagingSystem;
    }

    public boolean isNewPlayer() {
        return newPlayer;
    }

    public void setNewPlayer(Boolean newPlayer) {
        this.newPlayer = newPlayer;
    }

    public Player getPlayerByName(String name) {
        Player p = null;
        for (Player search : getPlayerList()) {
            if (search.getName().equalsIgnoreCase(name)) {
                p = search;
            }
        }
        return p;
    }

    public String getMainPlayerUUID() {
        return playerUUID;
    }

    public Player getPlayerByUUID(String uuid) {
        Player returner = null;
        System.out.println("Searching for player with uuid " + uuid);
        ArrayList<Player> players = (ArrayList<Player>) getPlayerList().clone();
        Iterator playerIterator = getPlayerList().iterator();
        while (playerIterator.hasNext()) {
        Player p = (Player) playerIterator.next();
            System.out.println("traversing through player list " + p + " for uuid " + uuid);
            if (p.getUUID().equals(uuid)) {
                returner = p;
            }
        }
        return returner;
    }

    public void processSyncMessage(ArrayList<Player> players) {
        if (up) {

            System.out.println("\n###########");
            boolean alreadyAdded = false;
            for (Player p : players) {
                System.out.println(p);
                for (Player fromList : getPlayerList()) {
                    if (fromList.getUUID() != null && fromList.getUUID().equalsIgnoreCase(p.getUUID())) {
                        alreadyAdded = true;
                    }

                }
                if (!alreadyAdded) {

                    syncPlayer(p);
                }
                alreadyAdded = false;
            }




            System.out.println("##########\n");
            markForPlayerSync = false;
            newPlayer = true;
            needsToBeSynced.clear();
        } else {
            needsToBeSynced.addAll(players);

        }
    }

    private void syncPlayer(Player syncMessagePlayer) {

        Player syncedPlayer = new Player(this, syncMessagePlayer.getID(), syncMessagePlayer.getName(), syncMessagePlayer.getUUID());
        newPlayer = true;
        getPlayerList().add(syncedPlayer);
    }

    public void removePlayer(String uUID) {
      needsToBeDeleted.add(uUID);
        newPlayer = true;
    }
}